/*
 * D I S C L A I M E R          
 * 
 * WARNING: ANY USE BY YOU OF THE SAMPLE CODE PROVIDED IS AT YOUR OWN RISK.
 * 
 * Authorize.Net provides this code "as is" without warranty of any kind, either
 * express or implied, including but not limited to the implied warranties of 
 * merchantability and/or fitness for a particular purpose.   
 * Authorize.Net owns and retains all right, title and interest in and to the 
 * Automated Recurring Billing intellectual property.
 */

using System;
using System.Collections;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.Net;

using System.Configuration;


namespace ArbApiSample
{
    public static class Program
    {
        // This is the URL of the API server.
        // You must give this a valid value to successfully execute this sample code.

       
        //private static string _apiUrl = @"https://apitest.authorize.net/xml/v1/request.api";
        private static string _apiUrl = @"https://api.authorize.net/xml/v1/request.api";
       

        // This is the user's Payment Gateway account login name used for transaction processing.
        // You must give this a valid value to successfully execute this sample code.
        private static string _userLoginName = ConfigurationManager.AppSettings["AuthNetLoginID"];

        // This is the TransactionKey associated with that account.
        // You must give this a valid value to successfully execute this sample code.
        private static string _transactionKey = ConfigurationManager.AppSettings["AuthNetTransKey"];

        // This will be set by CreateSubscription and used for the UpdateSubscription 
        // and CancelSubscription
        private static string _subscriptionId;


        // controls whether the XML request and response are written to the console
        private static bool _dumpXml;


        // ----------------------------------------------------------------------------------------
        /// <summary>
        /// The main entry point for the application.
        /// <param name="args"></param>
        /// </summary>
        // ----------------------------------------------------------------------------------------
        static void Main(string[] args)
        {
            bool bResult = true;

            Console.WriteLine("API server: " + _apiUrl);

            if (bResult)
            {
                // Set this to true if you want to see the XML requests and responses dumped
                // to the console window. Set to false otherwise.
                _dumpXml = false;

                // Create a new subscription
                Console.WriteLine("\r\n\r\nThis call to ARBCreateSubscription should be successful.");
                bResult = CreateSubscription(false);

                // This example will generate an error because a duplicate subscription
                // is being created.
                Console.WriteLine("\r\n\r\nThis call to ARBCreateSubscription shows how to handle errors.");
                CreateSubscription(false);

                // This example will generate an error because the Amount is missing causing
                // a schema validatation error to occur on the server side.
                Console.WriteLine("\r\n\r\nThis call to ARBCreateSubscription shows how to process ErrorResponse.");
                CreateSubscription(true);

                // Update the subscription that was just created
                if (bResult && _subscriptionId != null)
                {
                    Console.WriteLine("\r\n\r\nThis call to ARBUpdateSubscription should be successful.");
                    bResult = UpdateSubscription();

                    // Cancel the subscription that was just created
                    if (bResult)
                    {
                        Console.WriteLine("\r\n\r\nThis call to ARBCancelSubscription should be successful.");
                        bResult = CancelSubscription();
                    }

                    // Get the status of the subscription we just canceled
                    bResult = GetStatusSubscription();
                }
            }

            Console.WriteLine("\r\nPress any key to exit.");
            Console.ReadKey();
        }

        // ----------------------------------------------------------------------------------------
        /// <summary>
        /// Create a new ARB Subscription.
        /// </summary>
        // ----------------------------------------------------------------------------------------
        public static bool CreateSubscription(bool forceXmlError)
        {
            bool bResult = true;

            Console.WriteLine("\r\nCreate subscription");

            // This is the API interface object
            

            ARBCreateSubscriptionRequest createSubscriptionRequest = new ARBCreateSubscriptionRequest();
            
            // Populate the subscription request with test data
            PopulateSubscription(createSubscriptionRequest);
            if (forceXmlError) createSubscriptionRequest.merchantAuthentication = null;

            // The response type will normally be ARBCreateSubscriptionResponse.
            // However, in the case of an error such as an XML parsing error, the response
            //   type will be ErrorResponse.

            object response = null;
            XmlDocument xmldoc = null;
            bResult = PostRequest(createSubscriptionRequest, out xmldoc);

            if (bResult) bResult = ProcessXmlResponse(xmldoc, out response);

            if (bResult) ProcessResponse(response);

            if (!bResult)
            {
                Console.WriteLine("An unexpected error occurred processing this request.");
            }

            return bResult;

        }

        // ----------------------------------------------------------------------------------------
        /// <summary>
        /// Update an existing ARB subscription using the subscription ID returned by the create.
        /// </summary>
        // ----------------------------------------------------------------------------------------
        private static bool UpdateSubscription()
        {
            bool bResult = true;

            Console.WriteLine("\r\nUpdate subscription");

            // This is the API interface object
            ARBUpdateSubscriptionRequest updateSubscriptionRequest = new ARBUpdateSubscriptionRequest();

            // Populate the subscription request with test data
            PopulateSubscription(updateSubscriptionRequest);

            // The response type will normally be ARBUpdateSubscriptionResponse.
            // However, in the case of an error such as an XML parsing error, the response
            //   type will be ErrorResponse.

            object response = null;
            XmlDocument xmldoc = null;
            bResult = PostRequest(updateSubscriptionRequest, out xmldoc);

            if (bResult) bResult = ProcessXmlResponse(xmldoc, out response);

            if (bResult) ProcessResponse(response);

            if (!bResult)
            {
                Console.WriteLine("An unexpected error occurred processing this request.");
            }

            return bResult;

        }

        // ----------------------------------------------------------------------------------------
        /// <summary>
        /// Cancel an existing ARB subscription using the subscription ID returned by the create.
        /// </summary>
        // ----------------------------------------------------------------------------------------
        private static bool CancelSubscription()
        {
            bool bResult = true;

            Console.WriteLine("\r\nCancel subscription");

            // This is the API interface object
            ARBCancelSubscriptionRequest cancelSubscriptionRequest = new ARBCancelSubscriptionRequest();

            // Populate the subscription request with test data
            PopulateSubscription(cancelSubscriptionRequest);

            // The response type will normally be ARBCancelSubscriptionRequest.
            // However, in the case of an error such as an XML parsing error, the response
            //   type will be ErrorResponse.

            object response = null;
            XmlDocument xmldoc = null;
            bResult = PostRequest(cancelSubscriptionRequest, out xmldoc);

            if (bResult) bResult = ProcessXmlResponse(xmldoc, out response);

            if (bResult) ProcessResponse(response);

            if (!bResult)
            {
                Console.WriteLine("An unexpected error occurred processing this request.");
            }

            return bResult;

        }

        // ----------------------------------------------------------------------------------------
        /// <summary>
        /// Get the status of an existing ARB subscription 
        /// </summary>
        // ----------------------------------------------------------------------------------------
        private static bool GetStatusSubscription()
        {
            bool bResult = true;

            Console.WriteLine("\r\nGet subscription status");

            // This is the API interface object
            ARBGetSubscriptionStatusRequest statusSubscriptionRequest = new ARBGetSubscriptionStatusRequest();

            // Populate the subscription request with test data
            PopulateSubscription(statusSubscriptionRequest);

            // The response type will normally be ARBGetSubscriptionStatusRequest.
            // However, in the case of an error such as an XML parsing error, the response
            //   type will be ErrorResponse.

            object response = null;
            XmlDocument xmldoc = null;
            bResult = PostRequest(statusSubscriptionRequest, out xmldoc);

            if (bResult) bResult = ProcessXmlResponse(xmldoc, out response);

            if (bResult) ProcessResponse(response);
            ARBGetSubscriptionStatusResponse theResponse = (ARBGetSubscriptionStatusResponse)response;
            Console.WriteLine("Status Text: " + theResponse.Status + "\r\n");


            if (!bResult)
            {
                Console.WriteLine("An unexpected error occurred processing this request.");
            }

            return bResult;

        }

        // ----------------------------------------------------------------------------------------
        /// <summary>
        /// Fill in the given request with test data to create a new subscription.
        /// </summary>
        /// <param name="sub"></param>
        // ----------------------------------------------------------------------------------------
        private static void PopulateSubscription(ARBCreateSubscriptionRequest request)
        {
            ARBSubscriptionType sub = new ARBSubscriptionType();

           
            creditCardType creditCard = new creditCardType();

            sub.name = "Sample subscription";

            creditCard.cardNumber = "4111111111111111";
            creditCard.expirationDate = "2029-07";  // required format for API is YYYY-MM

            sub.payment = new paymentType();
            sub.payment.Item = creditCard;

            sub.billTo = new nameAndAddressType();
            sub.billTo.firstName = "Test";
            sub.billTo.lastName = "Kumar";

            // Create a subscription that is 12 monthly payments starting on Jan 1, 2019
            // with 1 month free (trial period)

            sub.paymentSchedule = new paymentScheduleType();
            sub.paymentSchedule.startDate = new DateTime(2010, 10, 1); //YYYY-MM-DD
            sub.paymentSchedule.startDateSpecified = true;
            
            sub.paymentSchedule.totalOccurrences = 12;
            sub.paymentSchedule.totalOccurrencesSpecified = true;

            // free 1 month trial
            //sub.paymentSchedule.trialOccurrences = 1;
            //sub.paymentSchedule.trialOccurrencesSpecified = true;
            //sub.trialAmount = 0.00M;
            //sub.trialAmountSpecified = true;

            sub.amount = 10.49M;
            sub.amountSpecified = true;

            sub.paymentSchedule.interval = new paymentScheduleTypeInterval();
            sub.paymentSchedule.interval.length = 1;
            //sub.paymentSchedule.interval.unit = ARBSubscriptionUnitEnum.months;
            sub.paymentSchedule.interval.unit = ARBSubscriptionUnitEnum.days;

            sub.order = new orderType();
            sub.order.invoiceNumber = "IN0001";

            sub.customer = new customerType();
            sub.customer.email = "test@example.com";

            // Include authentication information
            PopulateMerchantAuthentication((ANetApiRequest)request);

            request.subscription = sub;
        }


        // ----------------------------------------------------------------------------------------
        /// <summary>
        /// Fill in the given request with test data used to update the subscription.
        /// </summary>
        /// <param name="sub"></param>
        // ----------------------------------------------------------------------------------------
        private static void PopulateSubscription(ARBUpdateSubscriptionRequest request)
        {
            ARBSubscriptionType sub = new ARBSubscriptionType();
            creditCardType creditCard = new creditCardType();

            request.subscriptionId = _subscriptionId;

            // For this sample I just want to update the credit card information.
            creditCard.cardNumber = "4111111111111111";
            creditCard.expirationDate = "2029-07";  // required format for API is YYYY-MM
            sub.payment = new paymentType();
            sub.payment.Item = creditCard;

            // Include authentication information
            PopulateMerchantAuthentication((ANetApiRequest)request);

            request.subscription = sub;
        }

        // ----------------------------------------------------------------------------------------
        /// <summary>
        /// Fill in the given request with test data used to cancel the subscription.
        /// </summary>
        /// <param name="sub"></param>
        // ----------------------------------------------------------------------------------------
        private static void PopulateSubscription(ARBCancelSubscriptionRequest request)
        {
            ARBSubscriptionType sub = new ARBSubscriptionType();
            creditCardType creditCard = new creditCardType();

            request.subscriptionId = _subscriptionId;

            // Include authentication information
            PopulateMerchantAuthentication((ANetApiRequest)request);
        }

        // ----------------------------------------------------------------------------------------
        /// <summary>
        /// Fill in the given request with test data used to retrieve the status of the subscription.
        /// </summary>
        /// <param name="sub"></param>
        // ----------------------------------------------------------------------------------------
        private static void PopulateSubscription(ARBGetSubscriptionStatusRequest request)
        {
            ARBSubscriptionType sub = new ARBSubscriptionType();
            creditCardType creditCard = new creditCardType();

            request.subscriptionId = _subscriptionId;

            // Include authentication information
            PopulateMerchantAuthentication((ANetApiRequest)request);
        }

        // ----------------------------------------------------------------------------------------
        /// <summary>
        /// Fill in the merchant authentication. This data is required for all API methods.
        /// </summary>
        /// <param name="request"></param>
        // ----------------------------------------------------------------------------------------
        private static void PopulateMerchantAuthentication(ANetApiRequest request)
        {
            request.merchantAuthentication = new merchantAuthenticationType();
            request.merchantAuthentication.name = _userLoginName;
            request.merchantAuthentication.transactionKey = _transactionKey;
            request.refId = "Sample";
        }

        // ----------------------------------------------------------------------------------------
        /// <summary>
        /// Send the request to the API server and load the response into an XML document.
        /// An XmlSerializer is used to form the XML used in the request to the API server. 
        /// The response from the server is also XML. An XmlReader is used to process the
        /// response stream from the API server so that it can be loaded into an XmlDocument.
        /// </summary>
        /// <param name="request"></param>
        /// <returns>
        /// True if successful, false if not. If true then the specified xmldoc will contain the
        /// response received from the API server.
        /// </returns>
        // ----------------------------------------------------------------------------------------
        private static bool PostRequest(object apiRequest, out XmlDocument xmldoc)
        {
            bool bResult = false;
            XmlSerializer serializer;

            xmldoc = null;

            try
            {
                HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(_apiUrl);
                webRequest.Method = "POST";
                webRequest.ContentType = "text/xml";
                webRequest.KeepAlive = true;

                // Serialize the request
                serializer = new XmlSerializer(apiRequest.GetType());
                XmlWriter writer = new XmlTextWriter(webRequest.GetRequestStream(), Encoding.UTF8);
                serializer.Serialize(writer, apiRequest);
                writer.Close();

                if (_dumpXml)
                {
                    Console.WriteLine("\r\n\r\n");
                    StreamWriter consoleOutput = new StreamWriter(Console.OpenStandardOutput());
                    consoleOutput.AutoFlush = true;
                    serializer.Serialize(consoleOutput, apiRequest);
                    consoleOutput.Close();
                    Console.WriteLine("\r\n\r\n");
                }
 
                // Get the response
                WebResponse webResponse = webRequest.GetResponse();

                // Load the response from the API server into an XmlDocument.
                xmldoc = new XmlDocument();
                xmldoc.Load(XmlReader.Create(webResponse.GetResponseStream()));

                if (_dumpXml)
                {
                    XmlWriterSettings settings = new XmlWriterSettings();
                    settings.Indent = true;
                    settings.Encoding = Encoding.ASCII;
                    XmlWriter consoleWriter = XmlWriter.Create(Console.Out,settings);
                    xmldoc.WriteTo(consoleWriter);
                    consoleWriter.Close();
                    Console.WriteLine("\r\n\r\n");
                }
 
                bResult = true;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.GetType().ToString() + ": " + ex.Message);
                bResult = false;
            }

            return bResult;
        }

        // ----------------------------------------------------------------------------------------
        /// <summary>
        /// Deserialize the given XML document into the correct object type using the root
        /// node to determine the type of output object.
        /// 
        /// For any given API request the response can be one of two types:
        ///    ErorrResponse or [methodname]Response. 
        /// For example, the ARBCreateSubscriptionRequest would normally result in a response of
        /// ARBCreateSubscriptionResponse. This is also the name of the root node of the response.
        /// This name can be used to deserialize the response into local objects. 
        /// </summary>
        /// <param name="xmldoc">
        /// This is the XML document to process. It holds the response from the API server.
        /// </param>
        /// <param name="apiResponse">
        /// This will hold the deserialized object of the appropriate type.
        /// </param>
        /// <returns>
        /// True if successful, false if not.
        /// </returns>
        // ----------------------------------------------------------------------------------------
        private static bool ProcessXmlResponse(XmlDocument xmldoc, out object apiResponse)
        {
            bool bResult = true;
            XmlSerializer serializer;

            apiResponse = null;

            try
            {
                // Use the root node to determine the type of response object to create
                switch (xmldoc.DocumentElement.Name)
                {
                    case "ARBCreateSubscriptionResponse":
                        serializer = new XmlSerializer(typeof(ARBCreateSubscriptionResponse));
                        apiResponse = (ARBCreateSubscriptionResponse)serializer.Deserialize(new StringReader(xmldoc.DocumentElement.OuterXml));
                        break;

                    case "ARBUpdateSubscriptionResponse":
                        serializer = new XmlSerializer(typeof(ARBUpdateSubscriptionResponse));
                        apiResponse = (ARBUpdateSubscriptionResponse)serializer.Deserialize(new StringReader(xmldoc.DocumentElement.OuterXml));
                        break;

                    case "ARBCancelSubscriptionResponse":
                        serializer = new XmlSerializer(typeof(ARBCancelSubscriptionResponse));
                        apiResponse = (ARBCancelSubscriptionResponse)serializer.Deserialize(new StringReader(xmldoc.DocumentElement.OuterXml));
                        break;

                    case "ARBGetSubscriptionStatusResponse":
                        serializer = new XmlSerializer(typeof(ARBGetSubscriptionStatusResponse));
                        apiResponse = (ARBGetSubscriptionStatusResponse)serializer.Deserialize(new StringReader(xmldoc.DocumentElement.OuterXml));
                        break;

                    case "ErrorResponse":
                        serializer = new XmlSerializer(typeof(ANetApiResponse));
                        apiResponse = (ANetApiResponse)serializer.Deserialize(new StringReader(xmldoc.DocumentElement.OuterXml));
                        break;

                    default:
                        Console.WriteLine("Unexpected type of object: " + xmldoc.DocumentElement.Name);
                        bResult = false;
                        break;
                }
            }
            catch (Exception ex)
            {
                bResult = false;
                apiResponse = null;
                Console.WriteLine(ex.GetType().ToString() + ": " + ex.Message);
            }

            return bResult;
        }

        // ----------------------------------------------------------------------------------------
        /// <summary>
        /// Determine the type of the response object and process accordingly.
        /// Since this is just sample code the only processing being done here is to write a few
        /// bits of information to the console window.
        /// </summary>
        /// <param name="response"></param>
        // ----------------------------------------------------------------------------------------
        private static void ProcessResponse(object response)
        {
            // Every response is based on ANetApiResponse so you can always do this sort of type casting.
            ANetApiResponse baseResponse = (ANetApiResponse)response;

            // Write the results to the console window
            Console.Write("Result: ");
            Console.WriteLine(baseResponse.messages.resultCode.ToString());

            // If the result code is "Ok" then the request was successfully processed.
            if (baseResponse.messages.resultCode == messageTypeEnum.Ok)
            {
                // CreateSubscription is the only method that returns additional data
                if (response.GetType() == typeof(ARBCreateSubscriptionResponse))
                {
                    ARBCreateSubscriptionResponse createResponse = (ARBCreateSubscriptionResponse)response;
                    _subscriptionId = createResponse.subscriptionId;

                    Console.WriteLine("Subscription ID: " + _subscriptionId);
                }
            }
            else
            {
                // Write error messages to console window
                for (int i = 0; i < baseResponse.messages.message.Length; i++)
                {
                    Console.WriteLine("[" + baseResponse.messages.message[i].code
                            + "] " + baseResponse.messages.message[i].text);
                }
            }
        }

    }
}
